import java.io.IOException;

/**
    Represents a Process Control Block
    @author Morton & Uemura
 */
public class Process {
	/** ID */
	private String ID;

	/** Arrival Time*/
	private double arrivalTime;

	/** Remaining Time to completion */
	private double currentRemainingTime;

	/** Counter for CPU usage time */
	private double runTime;
	
	/** Indicator for current IO device if any */
	private String currentDevice;
	
	/** Current Ready Queue ID */
	private int currentQueueNumber;

	/** Current Priority ID */
	private int currentPriorityID;

	/** Ready Queue Allocated Time Quantum */
	private double currentTimeSlice;

	/** Probability of a Disk Request */
	private double probDiskRequest;

	/** Average Disk Time */
	private double aveDiskTime;

	/** Calculated Disk Processing Time */
	private double diskLifeTime;

	/** Probability of Printer 1 Request */
	private double probPr1Request;

	/** Average Printer 1 Time */
	private double avePr1Time;

	/** Calculated Printer 1 Processing Time */
	private double pr1LifeTime;

	/** Probability of Printer 2 Request */
	private double probPr2Request;

	/** Average Printer 2 Time */
	private double avePr2Time;

	/** Calculated Printer 2 Processing Time */
	private double pr2LifeTime;

	/** Indicator for IO Requested */
	private boolean deviceInterrupt;
	
	/** Indicator for Feedback */
	private boolean feedback;
	
	// Moved From ProcessTime class
	/** True if already added to CPU. */
	private boolean addedToCPU;
	
	/** Total Real Time Execution */
	private double turnAroundTime;

	/** Time Spent in Ready Queue */
	private double waitTime;

	/** Waited Time of first CPU execution */
	private double responseTime;
	
	/** Default indicator for INVALID values */
	private final int INVALID = -1;
	
	/**
	   Constructs a Process
	   @param ID Process ID 
	   @param arrTime Arrival Time
	   @param cpuTime CPU Required Time
	   @param probDiskReq Probability of Disk Request
	   @param aveDiskTime Average Disk Time
	   @param probPr1 Probability of Printer 1 Request
	   @param avePr1Time Average Printer 1 Time
	   @param probPr2 Probability of Printer 2 Request
	   @param avePr2Time Average Printer 2 Time
	   @param priority Priority Level
	   @throws IOException if invalid priority is given. Valid range is
	    [0-15] inclusive.
	 */
	public Process(String ID, double arrTime, double cpuTime, double probDiskReq,
			double aveDiskTime, double probPr1, double avePr1Time, double probPr2,
			double avePr2Time, int priority) throws IOException {
		
		setID(ID);
		setArrivalTime(arrTime);
		setCurrentRemainingTime(cpuTime);
		setCPUTimeUsed(0);

		setProbDiskRequest(probDiskReq);
		setAveDiskTime(aveDiskTime);
		setDiskLifeTime(calculateTime(aveDiskTime));

		setProbPr1Request(probPr1);
		setAvePr1Time(avePr1Time);
		setPr1LifeTime(calculateTime(avePr1Time));

		setProbPr2Request(probPr2);
		setAvePr2Time(avePr2Time);
		setPr2LifeTime(calculateTime(avePr2Time));

		setCurrentPriorityID(priority);
		setCurrentQueueNumber(priority);
		setCurrentTimeSlice(INVALID);
		feedback = false;
		currentDevice = new String();
		
		setAddedToCPU(false);
	}

	// Helper Functions
	/**
	   Calculates Time to spent in device(Disk, Printer 1 & Printer 2)
	 */
	public static double calculateTime(double aveTime) 
	{
		double avg = Simulator.getOneDecimal((double) (2 * aveTime * Math.random()));
		return avg;
	}

	// Helper functions to reset remaining time.
	/**
	   Resets Disk Remaining Time
	 */
	public void resetDiskRemainingTime()
	{
		this.diskLifeTime = calculateTime(aveDiskTime);
	}
	
	/**
	   Resets Printer 1 Remaining Time
	 */
	public void resetPr1RemainingTime()
	{
		this.pr1LifeTime = calculateTime(avePr1Time);
	}
	
	/**
	   Resets Printer 2 Remaining Time
	 */
	public void resetPr2RemainingTime()
	{
		this.pr2LifeTime = calculateTime(avePr2Time);
	}

	/**
	   Displays Process Information
	 */
	public void printInfo() {
		System.out.println("Process ID:\t" + this.getID() + "\nArrTime:\t"
				+ this.getArrivalTime() + "\nTot CPU Time:\t"
				+ this.getCurrentRemainingTime() + "\nProb. Disk Req:\t"
				+ this.getProbDiskRequest() + "\nAve. Disk Time:\t"
				+ this.getAveDiskTime() + "\nDisk Life TIme:\t"
				+ this.getDiskLifeTime() + "\nProb. Pr1 Req:\t"
				+ this.getProbPr1Request() + "\nAve. Pr1 Time:\t"
				+ this.getAvePr1Time() + "\nPr1 Life Time:\t"
				+ this.getPr1LifeTime() + "\nProb. Pr2 Req:\t"
				+ this.getProbPr2Request() + "\nAve. Pr2 TIme:\t"
				+ this.getAvePr2Time() + "\nPr2 Life Time:\t"
				+ this.getPr2LifeTime() + "\nPriority ID:\t"
				+ this.getCurrentPriorityID() + "\nQueue ID:\t"
				+ this.getCurrentQueueNumber() + "\n");
	}

	// Start Getter, Setter
	/**
	   Sets ID
	   @param ID
	 */
	public void setID(String iD) {
		ID = iD;
	}

	/**
	   Gets ID
	   @return ID
	 */
	public String getID() {
		return ID;
	}

	/**
	   Sets Arrival Time
	   @param arrivalTime Process Arrival Time
	 */
	public void setArrivalTime(double arrivalTime) {
		this.arrivalTime = arrivalTime;
	}

	/**
	   Gets Arrival Time
	   @return Process Arrival Time
	 */
	public double getArrivalTime() {
		return arrivalTime;
	}

	/**
	   Sets Process Remaining Time
	   @param currentRemainingTime Remaining Time
	 */
	public void setCurrentRemainingTime(double currentRemainingTime) {
		this.currentRemainingTime = currentRemainingTime;
	}

	/**
	   Gets Process Remaining Time
	   @return Remaining Time
	 */
	public double getCurrentRemainingTime() {
		return currentRemainingTime;
	}
	
	/**
	   Gets CPU Usage Time
	   @return CPU Usage
	 */
	public double getCPUTimeUsed() {
		return runTime;
	}
	
	/**
	   Sets Time spent in CPU
	   @param ranFor CPU run time
	 */
	public void setCPUTimeUsed(double ranFor) {
		runTime += ranFor;
	}

	/**
	   Sets Ready Queue Number To Insert To
	   @param pNum Priority Level
	   @throws IOException if invalid Priority Level
	 */
	public void setCurrentQueueNumber(int pNum) throws IOException {
		if (pNum >= 0 && pNum <= 3)
			this.currentQueueNumber = 1;
		else if (pNum >= 4 && pNum <= 7)
			this.currentQueueNumber = 2;
		else if (pNum >= 8 && pNum <= 11)
			this.currentQueueNumber = 3;
		else if (pNum >= 12 && pNum <= 15)
			this.currentQueueNumber = 4;
		else
			throw new IOException("Priority Out of Range");
	}

	/**
	   Gets Queue Number
	   @return Queue Number
	 */
	public int getCurrentQueueNumber() {
		return currentQueueNumber;
	}

	/**
	   Sets Priority ID/Level
	   @param currentPriorityID Priority ID
	 */
	public void setCurrentPriorityID(int currentPriorityID) {
		this.currentPriorityID = currentPriorityID;
	}

	/**
	   Gets Priority ID/Level
	   @return Priority ID
	 */
	public int getCurrentPriorityID() {
		return currentPriorityID;
	}

	/**
	   To Set Time Quantum to corresponding Queue
	   @param timeSlice Time Quantum 
	 */
	public void setCurrentTimeSlice(int timeSlice) {
			this.currentTimeSlice = timeSlice;
	}

	/**
	   Returns Current Queue Time Quantum
	   @return Time Quantum
	 */
	public double getCurrentTimeSlice() {
		return currentTimeSlice;
	}

	/**
	   Sets Probability of Disk Request
	   @param probDiskRequest Probability of Disk Request
	 */
	public void setProbDiskRequest(double probDiskRequest) {
		this.probDiskRequest = probDiskRequest;
	}

	/**
	   Gets Probability of Disk Request
	   @return Probability of Disk Request
	 */
	public double getProbDiskRequest() {
		return probDiskRequest;
	}

	/**
	   Sets Average Disk Time
	   @param aveDiskTime Average Disk Time
	 */
	public void setAveDiskTime(double aveDiskTime) {
		this.aveDiskTime = aveDiskTime;
	}

	/**
	   Gets Average Disk Time
	   @return Average Disk Time
	 */
	public double getAveDiskTime() {
		return aveDiskTime;
	}

	/**
	   Sets Disk Life Time/Remaining Time
	   @param diskLifeTime Disk Life Time
	 */
	public void setDiskLifeTime(double diskLifeTime) {
		this.diskLifeTime = diskLifeTime;
	}

	/**
	   Gets Disk Life Time/Remaining Time
	   @return Disk Life Time
	 */
	public double getDiskLifeTime() {
		return diskLifeTime;
	}

	/**
	   Sets Probability of Printer 1 Request
	   @param probPr1Request Probability of Printer 1 Request
	 */
	public void setProbPr1Request(double probPr1Request) {
		this.probPr1Request = probPr1Request;
	}

	/**
	   Gets Probability of Printer 1 Request
	   @return Probability of Printer 1 Request
	 */
	public double getProbPr1Request() {
		return probPr1Request;
	}

	/**
	   Sets Average Printer 1 Time
	   @param avePr1Time Average Printer 1 Time
	 */
	public void setAvePr1Time(double avePr1Time) {
		this.avePr1Time = avePr1Time;
	}

	/**
	   Gets Average Printer 1 Time
	   @return Average Printer 1 Time
	 */
	public double getAvePr1Time() {
		return avePr1Time;
	}

	/**
	   Set Remaining Printer 1 Time
	   @param pr1LifeTime Remaining Printer 1 time
	 */
	public void setPr1LifeTime(double pr1LifeTime) {
		this.pr1LifeTime = pr1LifeTime;
	}

	/**
	   Gets Remaining Printer 1 Time
	   @return Remaining Printer 1 Time 
	 */
	public double getPr1LifeTime() {
		return pr1LifeTime;
	}

	/**
	   Sets Probability of Printer 2 Request
	   @param probPr2Request Probability of Printer 2 Request
	 */
	public void setProbPr2Request(double probPr2Request) {
		this.probPr2Request = probPr2Request;
	}

	/**
	   Gets Probability of Printer 2 Request
	   @return Probability of Printer 2 Request
	 */
	public double getProbPr2Request() {
		return probPr2Request;
	}

	/**
	   Sets Average Time of Printer 2
	   @param avePr2Time Average Time of Printer 2
	 */
	public void setAvePr2Time(double avePr2Time) {
		this.avePr2Time = avePr2Time;
	}

	/**
	   Gets Average Printer 2 Time
	   @return Average Printer 2 Time
	 */
	public double getAvePr2Time() {
		return avePr2Time;
	}

	/**
	   Remaining Printer 2 Time
	   @param pr2LifeTime Remaining Printer 2 Time
	 */
	public void setPr2LifeTime(double pr2LifeTime) {
		this.pr2LifeTime = pr2LifeTime;
	}

	/**
	   Get Remaining Printer 2 Time
	   @return Remaining Printer 2 Time
	 */
	public double getPr2LifeTime() {
		return pr2LifeTime;
	}

	/**
	   Sets IO Requested 
	   @param b IO Requested Flag
	 */
	public void setDeviceInterrupt(boolean b) {
		this.deviceInterrupt = b;
	}

	/**
	   Gets Device Interrupt
	   @return True is interrupted, else false
	 */
	public boolean getDeviceInterrupt() {
		return this.deviceInterrupt;
	}
	
	/**
	   Conducts Feedback If no IO Request or Completion
	    was made after running in CPU for current Queue
	    time quantum
	   @param fAmt Priority Level
	 */
	public void doFeedback(int fAmt)
	{
		
		if (this.getFeedback() && this.getCurrentPriorityID() < 12)
		{
			this.setCurrentPriorityID(currentPriorityID + fAmt);
			try {
				this.setCurrentQueueNumber(currentPriorityID);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.feedback = false;
	}
	
	/**
	   Checks current device the process is in,
	    and returns remaining time in that device.
	   @return	remaining time
	 */
	public double getCurrentDeviceLifetime() {
		String cDevice = this.getCurrentDevice();
		if (cDevice.equals("CPU"))
			return this.getCurrentRemainingTime();
		else if (cDevice.equals("Disk"))
			return this.getDiskLifeTime();
		else if (cDevice.equals("Pr1"))
			return this.getPr1LifeTime();
		else if (cDevice.equals("Pr2"))
			return this.getPr2LifeTime();
		else
			return 0;
	}
	

	/**
	   Resets Current Device LifeTime
	 */
	public void resetDeviceLifetime() {
		String cDevice = this.getCurrentDevice();
		if (cDevice.equals("Disk"))
			this.resetDiskRemainingTime();
		else if (cDevice.equals("Pr1"))
			this.resetPr1RemainingTime();
		else if (cDevice.equals("Pr2"))
			this.resetPr2RemainingTime();
		
		this.setCurrentDevice(new String());
	}
	
	/**
	   Clock for Devices
	   @param tick one unit of increment
	 */
	public void decrementRemainingTime(double tick)
	{
		String cDevice = this.getCurrentDevice();
		if (cDevice.equals("CPU"))
			this.decrementCPU(tick);
		else if (cDevice.equals("Disk"))
			this.decrementDisk(tick);
		else if (cDevice.equals("Pr1"))
			this.decrementPr1(tick);
		else if (cDevice.equals("Pr2"))
			this.decrementPr2(tick);
	}
	
	/**
	   Tick Alert while in CPU
	   @param tick unit increment time
	 */
	public void decrementCPU(double tick)
	{
		this.currentRemainingTime -= tick;
		this.currentTimeSlice -= tick;
	}
	
	/**
	   Tick Alert while in Disk
	   @param tick unit increment time
	 */
	public void decrementDisk(double tick)
	{
		this.diskLifeTime -= tick;
	}
	
	/**
	   Tick Alert while in Printer 1
	   @param tick unit increment time
	 */
	public void decrementPr1(double tick)
	{
		this.pr1LifeTime -= tick;
	}
	
	/**
	   Tick Alert while in Printer 2
	   @param tick unit increment time
	 */
	public void decrementPr2(double tick)
	{
		this.pr2LifeTime -= tick;
	}
	
	/**
	   Set feedback
	   @param b true for on, false for off
	 */
	public void setFeedback(boolean b)
	{
		this.feedback = b;
	}
	
	/**
	   Indicates if feedback is on
	   @return true if on, false if off
	 */
	public boolean getFeedback()
	{
		return this.feedback;
	}

	/**
	   Response Time set on first execution in CPU
	   @param clockTime current clock time
	 */
	public void setResponseTime(double clockTime) 
	{
		if (! this.isAddedToCPU())
		{
			this.responseTime = clockTime - arrivalTime;
			this.setAddedToCPU(true);
		}
	}

	/**
	   Sets Total real time execution
	   @param clockTime current clock time
	 */
	public void setTurnAroundTime(double clockTime) {
		this.turnAroundTime = clockTime - this.arrivalTime;
	}

	/**
	   Gets Total real time execution
	 * @return Turn Around Time
	 */
	public double getTurnAroundTime() {
		return this.turnAroundTime;
	}

	/**
	   Counter to increment wait time while
	    in Ready Queue
	   @param tick unit increment time
	 */
	public void incrementWaitTime(double tick) {
		this.waitTime += tick;
	}

	/**
	   Returns Wait Time
	   @return Wait Time
	 */
	public double getWaitTime() {
		return this.waitTime;
	}

	/**
	   Returns Response Time
	 * @return Response Time
	 */
	public double getResponseTime() {
		return this.responseTime;
	}
	
	/**
	   Sets Current IO Device
	   @param device IO Device
	 */
	public void setCurrentDevice(String device) {
		currentDevice = device;
	}
	
	/**
	   Returns Current Device Name
	   @return Current Device Name
	 */
	public String getCurrentDevice () {
		return currentDevice;
	}

	/**
	   Sets flag to indicate Process moved to CPU
	   @param addedToCPU true if moved, else false
	 */
	public void setAddedToCPU(boolean addedToCPU) {
		this.addedToCPU = addedToCPU;
	}

	/**
	   To determine is this is in CPU or not
	   @return true if in CPU, else false.
	 */
	public boolean isAddedToCPU() {
		return addedToCPU;
	}
}
